주 콘텐츠로 건너뛰기

Qiskit 소개

이 노트북에서는 Qiskit으로 양자 Gate와 양자 Circuit을 프로그래밍하는 방법, 그리고 Qiskit 패턴을 사용하여 시뮬레이터와 실제 양자 컴퓨터에서 실행하는 방법을 살펴봅니다. 이후에는 정보를 인코딩하는 다양한 방법을 소개하고, 마지막으로 양자 텔레포테이션 보너스 예제를 다룹니다.

시작하기 전에

설치 및 설정 안내를 아직 따르지 않았다면 지금 따라 주세요. IBM Quantum™ 플랫폼 사용 설정 단계도 포함됩니다.

양자 컴퓨터와 상호작용하려면 Jupyter 개발 환경을 사용하는 것을 권장합니다. 권장 시각화 지원('qiskit[visualization]')도 함께 설치하세요. 이 예제의 두 번째 부분에는 matplotlib 패키지도 필요합니다.

양자 컴퓨팅 일반에 대해 배우고 싶다면 IBM Quantum Learning의 양자 정보 기초 과정을 방문하세요.

임포트

# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-aer qiskit-ibm-runtime
# Import necessary modules for this notebook
import time
import qiskit

from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector
from qiskit.visualization import plot_bloch_multivector, plot_state_qsphere
from qiskit_aer import AerSimulator
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import EstimatorV2 as Estimator
from qiskit_ibm_runtime import SamplerV2 as Sampler
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit.visualization import plot_histogram
print(qiskit.__version__)
2.3.1

하드웨어에서 양자 Circuit을 실행하려면 먼저 계정을 설정해야 합니다. 다음과 같이 진행하세요:

  1. 업그레이드된 IBM Quantum® 플랫폼으로 이동하세요.
  2. 위 그림과 같이 오른쪽 상단 모서리로 이동하여 API 토큰을 생성하고 안전한 위치에 복사하세요.
  3. 다음 셀에서 deleteThisAndPasteYourAPIKeyHere를 여러분의 API 키로 교체하세요.
  4. 위 그림과 같이 왼쪽 하단 모서리로 이동하여 인스턴스를 생성하세요. 반드시 오픈 플랜을 선택하세요.
  5. 인스턴스가 생성되면 연관된 CRN 코드를 복사하세요. 인스턴스가 보이지 않으면 새로고침이 필요할 수 있습니다.
  6. 아래 셀에서 deleteThisAndPasteYourCRNHere를 여러분의 CRN 코드로 교체하세요.

IBM Cloud® 계정 설정 방법에 대한 자세한 내용은 이 가이드를 참조하세요.

⚠️ 참고: API 키는 보안 비밀번호처럼 안전하게 취급하세요. 안전한 환경과 신뢰할 수 없는 환경 모두에서 API 키를 사용하는 방법에 대한 자세한 내용은 Cloud 설정 가이드를 참조하세요.

#your_api_key = "deleteThisAndPasteYourAPIKeyHere"
#your_crn = "deleteThisAndPasteYourCRNHere"

QiskitRuntimeService.save_account(
channel="ibm_quantum_platform",
token=your_api_key,
instance=your_crn,
overwrite=True
)

1. 양자 Gate와 양자 Circuit

양자 Circuit은 양자 계산 모델로, 계산이 일련의 양자 Gate로 구성됩니다. 몇 가지 인기 있는 양자 Gate를 살펴보겠습니다.

X Gate

X Gate는 블로흐 구(Bloch sphere)의 X축을 중심으로 π\pi 라디안 회전하는 것과 같습니다. 0|0\rangle1|1\rangle로, 1|1\rangle0|0\rangle으로 매핑합니다. 고전 컴퓨터의 NOT Gate와 동일한 양자 연산으로, 비트 플립(bit-flip)이라고도 합니다.

X=(0110)X = \begin{pmatrix} 0 & 1 \\ 1 & 0 \\ \end{pmatrix}

# Let's apply an X-gate on a |0> qubit
qc = QuantumCircuit(1)
qc.x(0)
qc.draw(output='mpl')

Quantum circuit diagram

# Let's see Bloch sphere visualization
sv = Statevector(qc)
plot_bloch_multivector(sv)

Code output

H Gate

Hadamard Gate는 XX축과 ZZ축의 중간 축을 기준으로 π\pi 회전을 나타냅니다. 기저 상태 0|0\rangle0+12\frac{|0\rangle + |1\rangle}{\sqrt{2}}로 매핑하며, 이는 측정 결과가 1 또는 0일 확률이 동일함을 의미합니다. 이를 상태의 '중첩'이라고 하며, +|+\rangle으로도 표기합니다.

H=12(1111)H = \frac{1}{\sqrt{2}}\begin{pmatrix} 1 & 1 \\ 1 & -1 \\ \end{pmatrix}

# Let's apply an H-gate on a |0> qubit
qc = QuantumCircuit(1)
qc.x(0)
qc.h(0)
qc.draw(output='mpl')

Quantum circuit diagram

# Let's see Bloch sphere visualization
sv = Statevector(qc)
plot_bloch_multivector(sv)

Code output

CX Gate (CNOT Gate)

제어 NOT(CNOT 또는 CX) Gate는 두 Qubit에 작용합니다. 첫 번째 Qubit이 1|1\rangle일 때만 두 번째 Qubit에 NOT 연산(X Gate 적용과 동일)을 수행하며, 그렇지 않으면 변경하지 않습니다. 참고: Qiskit은 문자열에서 비트를 오른쪽에서 왼쪽으로 번호를 매깁니다.

CX=(1000010000010010)CX = \begin{pmatrix} 1 & 0 & 0 & 0\\ 0 & 1 & 0 & 0\\ 0 & 0 & 0 & 1\\ 0 & 0 & 1 & 0\\ \end{pmatrix}

# Let's apply a CX-gate on |11>
qc = QuantumCircuit(2)
qc.x(0)
qc.x(1)
qc.cx(0,1)
qc.draw(output='mpl')

Quantum circuit diagram

sv=Statevector(qc)
plot_state_qsphere(sv)

Code output

첫 번째 벨 상태를 만들어 봅니다.

ϕ+=12(00+11)|\phi^+ \rangle = \frac{1}{\sqrt 2}(|00 \rangle + |11 \rangle)

# Create a Bell state circuit

qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0,1)

# Draw the circuit
qc.draw("mpl")

Quantum circuit diagram

# Plot the state using q-sphere visualization
sv = Statevector(qc)
plot_state_qsphere(sv)
# q-sphere is useful for visualizing states when Bloch sphere fails to

Code output

두 번째 벨 상태를 만들어 봅니다.

ϕ=12(0011)|\phi^- \rangle = \frac{1}{\sqrt 2}(|00 \rangle - |11 \rangle)

# Create a circuit with the second Bell state

qc = QuantumCircuit(2)
qc.x(0)
qc.h(0)
qc.cx(0,1)

qc.draw("mpl")

Quantum circuit diagram

이에 대한 설명은 다음과 같습니다:

H1=12(01)=H|1\rangle=\frac{1}{\sqrt{2} }(|0\rangle-|1\rangle) = |-\rangle
# Get the statevector of the circuit
sv = Statevector(qc)

# Plot the state using qsphere visualization
plot_state_qsphere(sv)

Quantum circuit diagram

3-Qubit GHZ 상태를 만들어 봅니다.

GHZ=12(000+111)|GHZ \rangle = \frac{1}{\sqrt 2}(|000 \rangle + |111 \rangle)

# Create a circuit with 3-qubit GHZ state

qc= QuantumCircuit(3)
qc.h(0)
qc.cx(0,1)
qc.cx(0,2)

qc.draw("mpl")

Quantum circuit diagram

# Get the statevector of the circuit
sv = Statevector(qc)

# Plot the state using qsphere visualization
plot_state_qsphere(sv)

Quantum circuit diagram

Qiskit 로고 상태를 만들어 봅니다.

Qiskit=12(0010+1101)|Qiskit \rangle = \frac{1}{\sqrt 2}(|0010 \rangle + |1101 \rangle)

Centered Image
# Create a circuit with the Qiskit logo state

qc = QuantumCircuit(4)
qc.h(0)
qc.cx(0,1)
qc.cx(0,2)
qc.cx(0,3)
qc.x(1)

# Draw the circuit
qc.draw("mpl")

Quantum circuit diagram

# Get the statevector of the circuit
sv = Statevector(qc)

# Plot the state using qsphere visualization
plot_state_qsphere(sv)

Quantum circuit diagram

2. 간단한 양자 프로그램 만들기 및 실행

Qiskit 패턴을 사용하여 양자 프로그램을 작성하는 네 가지 단계는 다음과 같습니다:

  1. 문제를 양자 네이티브 형식으로 매핑합니다.

  2. Circuit과 연산자를 최적화합니다.

  3. 양자 프리미티브 함수를 사용하여 실행합니다.

  4. 결과를 분석합니다.

2.1 문제를 양자 네이티브 형식으로 매핑하기

양자 프로그램에서 양자 Circuit은 양자 명령을 표현하는 네이티브 형식이며, *연산자(operator)*는 측정할 관측량(observable)을 나타냅니다. Circuit을 생성할 때는 보통 새로운 QuantumCircuit 객체를 만든 후, 순서대로 명령을 추가합니다.

아래 코드 셀은 세 개의 Qubit이 서로 완전히 얽힌(entangle) 상태인 GHZ 상태를 생성하는 Circuit을 만듭니다.

Qiskit SDK는 LSb 0 비트 번호 체계를 사용하며, nthn^{th} 번째 자리의 값은 1n1 \ll n 또는 2n2^n입니다. 자세한 내용은 Qiskit SDK의 비트 순서 항목을 참고하세요.

# Create a GHZ state circuit

qc = QuantumCircuit(3)
qc.h(0)
qc.cx(0,1)
qc.cx(0,2)
# Draw the circuit
qc.draw("mpl")

Quantum circuit diagram

사용 가능한 모든 연산에 대해서는 문서의 QuantumCircuit을 참고하세요.

양자 Circuit을 생성할 때는 실행 후 어떤 유형의 데이터를 반환받을지도 고려해야 합니다. Qiskit은 데이터를 반환하는 두 가지 방법을 제공합니다. 측정하도록 선택한 Qubit 집합에 대한 확률 분포를 얻거나, 관측량의 기댓값(expectation value)을 얻을 수 있습니다. Qiskit primitives(3단계에서 자세히 설명)를 사용하여 이 두 가지 방법 중 하나로 Circuit을 측정하도록 워크로드를 준비하세요.

이 예제에서는 연산자(양자 상태를 변환하는 동작이나 과정을 나타내는 수학적 객체)를 사용하여 지정된 qiskit.quantum_info 서브모듈을 통해 기댓값을 측정합니다. 아래 코드 셀은 6개의 3-Qubit Pauli 연산자 ZZZ, ZZX, ZII, XXI, ZZI, III을 생성합니다.

# Set up six different observables.

observables_labels = ["ZZZ", "ZZX", "ZII", "XXI", "ZZI", "III"]

observables = [SparsePauliOp(label) for label in observables_labels]
print(observables)
[SparsePauliOp(['ZZZ'],
coeffs=[1.+0.j]), SparsePauliOp(['ZZX'],
coeffs=[1.+0.j]), SparsePauliOp(['ZII'],
coeffs=[1.+0.j]), SparsePauliOp(['XXI'],
coeffs=[1.+0.j]), SparsePauliOp(['ZZI'],
coeffs=[1.+0.j]), SparsePauliOp(['III'],
coeffs=[1.+0.j])]

여기서 ZZI 연산자는 텐서 곱 ZZIZ\otimes Z\otimes I의 축약 표기로, Qubit 2에서 Z를, Qubit 1에서 Z를 함께 측정하여 Qubit 2와 Qubit 1 사이의 상관관계에 대한 정보를 얻는다는 의미입니다. 이러한 기댓값은 일반적으로 Z2Z1\langle Z_2 Z_1 \rangle으로도 표기됩니다.

관측하는 상태가 3-Qubit GHZ 상태라면, Z2Z1\langle Z_2 Z_1 \rangle의 측정값은 1이어야 합니다.

2.2 Circuit과 연산자 최적화하기

장치에서 Circuit을 실행할 때는 Circuit에 포함된 명령 집합을 최적화하고, Circuit의 전체 깊이(depth, 대략 명령의 수)를 최소화하는 것이 중요합니다. 이렇게 하면 오류와 노이즈의 영향을 줄여 최상의 결과를 얻을 수 있습니다. 또한, Circuit의 명령은 Backend 장치의 명령 집합 아키텍처(ISA)를 준수해야 하며, 장치의 기본 Gate와 Qubit 연결성을 고려해야 합니다.

아래 코드는 작업을 제출할 실제 장치를 인스턴스화하고, 해당 Backend의 ISA에 맞게 Circuit과 연산자를 변환합니다. 이전에 자격 증명을 저장하지 않은 경우, 여기의 지침에 따라 API 토큰으로 인증하세요.

# Choose a real backend
service = QiskitRuntimeService(channel='ibm_quantum_platform',)
backend = service.least_busy(min_num_qubits=156)
# print backend details
print(
f"Name: {backend.name}\n"
f"Version: {backend.backend_version}\n"
f"No. of qubits: {backend.num_qubits}\n"
f"Processor type: {backend.processor_type}\n"
)
Name: ibm_marrakesh
Version: 1.0.21
No. of qubits: 156
Processor type: {'family': 'Heron', 'revision': '2'}
# option to use the AerSimulator instead of a real quantum device
seed_sim=42
backend=AerSimulator.from_backend(backend,seed_simulator=seed_sim)

Circuit을 ISA Circuit으로 Transpile합니다.

# Convert to an ISA circuit and layout-mapped observables.

pm = generate_preset_pass_manager(backend=backend, optimization_level=2)
isa_circuit = pm.run(qc)

isa_circuit.draw("mpl", idle_wires=False)

Quantum circuit diagram

mapped_observables = [
observable.apply_layout(isa_circuit.layout) for observable in observables
]
print(mapped_observables)
[SparsePauliOp(['IIIIIIIIIIIIIIIIZIIIIIZIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1.+0.j]), SparsePauliOp(['IIIIIIIIIIIIIIIIZIIIIIXIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1.+0.j]), SparsePauliOp(['IIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1.+0.j]), SparsePauliOp(['IIIIIIIIIIIIIIIIXIIIIIIIIIIIIIIIIIIXIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1.+0.j]), SparsePauliOp(['IIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1.+0.j]), SparsePauliOp(['IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1.+0.j])]

2.3 양자 Primitive를 사용하여 실행하기

양자 컴퓨터는 무작위 결과를 생성할 수 있으므로, 보통 Circuit을 여러 번 실행하여 출력값의 샘플을 수집합니다. Estimator 클래스를 사용하여 관측량의 기댓값을 추정할 수 있습니다. Estimator는 두 가지 Primitive 중 하나이며, 나머지 하나는 양자 컴퓨터에서 데이터를 가져오는 데 사용할 수 있는 Sampler입니다. 이 객체들은 primitive unified bloc(PUB)을 사용하여 선택된 Circuit, 관측량, 파라미터(해당하는 경우)를 실행하는 run() 메서드를 가지고 있습니다. 실제 양자 하드웨어에서 이 코드를 실행할 때는 양자 컴퓨터 고유의 노이즈를 줄이기 위해 오류 완화 및 억제 기법을 적용하는 것을 고려해 보세요.

# Construct the Estimator instance.
estimator = Estimator(mode=backend)
estimator.options.resilience_level = 1
estimator.options.default_shots = 5000

Estimator Primitive를 사용하여 작업을 제출합니다.

# One pub, with one circuit to run against six different observables.
job = estimator.run([(isa_circuit, mapped_observables)])

# Use the job ID to retrieve your job data later
print(f">>> Job ID: {job.job_id()}")
>>> Job ID: 97ecd036-1767-49b0-a1dc-c71638c3c3c4
/Users/jma/miniconda3/envs/3122/lib/python3.12/site-packages/qiskit_ibm_runtime/fake_provider/local_service.py:187: UserWarning: The resilience_level option has no effect in local testing mode.
warnings.warn("The resilience_level option has no effect in local testing mode.")

작업이 제출된 후, 현재 Python 인스턴스에서 작업이 완료될 때까지 기다리거나, job_id를 사용하여 나중에 데이터를 가져올 수 있습니다. (자세한 내용은 작업 검색 섹션을 참고하세요.)

작업이 완료된 후, 작업의 result() 속성을 통해 출력값을 확인합니다.

# This is the result of the entire submission.  You submitted one Pub,
# so this contains one inner result (and some metadata of its own).
job_result = job.result()

# This is the result from our single pub, which had six observables,
# so contains information on all six.
pub_result = job.result()[0]

이제 Sampler Primitive를 사용하여 Circuit을 실행할 수도 있습니다.

# We include the measurements in the circuit
qc.measure_all()
sampler = Sampler(mode=backend)
qc.draw(output="mpl")

Quantum circuit diagram

Sampler Primitive를 사용하여 작업을 제출합니다.

job_sampler = sampler.run(pm.run([qc]))

# Use the job ID to retrieve your job data later
print(f">>> Job ID: {job_sampler.job_id()}")
# Get the results
results_sampler = job_sampler.result()
>>> Job ID: a6ee4d2f-c80d-4a86-9a76-e4b1a74502e7

2.4 결과 분석

분석 단계는 일반적으로 측정 오류 완화(measurement error mitigation)나 제로 노이즈 외삽(ZNE, zero noise extrapolation) 등을 사용해 결과를 후처리하는 단계입니다. 이 결과를 추가 분석을 위해 다른 워크플로에 전달하거나, 핵심 값과 데이터를 시각화할 수도 있습니다. 일반적으로 이 단계는 풀고자 하는 문제에 따라 달라집니다. 이 예제에서는 Circuit에 대해 측정된 각 기댓값을 플롯합니다.

Estimator에 지정한 관측량의 기댓값과 표준편차는 job 결과의 PubResult.data.evsPubResult.data.stds 속성을 통해 접근할 수 있습니다. Sampler의 결과를 얻으려면 PubResult.data.meas.get_counts() 함수를 사용하세요. 이 함수는 비트 문자열을 키로, 카운트를 값으로 하는 dict를 반환합니다. 자세한 내용은 Sampler 시작하기를 참고하세요.

# Plot the result
from matplotlib import pyplot as plt
values = pub_result.data.evs
errors = pub_result.data.stds
# plotting graph
# Plotting with error bars
plt.errorbar(observables_labels, values, yerr=errors, fmt='-o', capsize=5)
plt.xlabel("Observables")
plt.ylabel("Values")
plt.title("Plot of Observables vs Values with Error Bars")
plt.grid(True)
plt.tight_layout()
plt.show()

Plot output

ZZIZZIIIIIII의 기댓값이 1임을 확인할 수 있습니다. ZZIZZI는 두 개의 마이너스 부호가 상쇄되고, IIIIII은 항등 연산자로 GHZ 상태를 변화시키지 않기 때문입니다. 나머지 관측량의 기댓값은 0인데, 이는 ZZ 연산자가 홀수 개의 마이너스 부호를 도입하거나, XX 연산자가 여러 Qubit을 뒤집어 겹치는 상태들이 직교하게 되기 때문입니다.

이제 Sampler의 결과를 플롯합니다.

counts_list = results_sampler[0].data.meas.get_counts()
print(counts_list)
print(f"Outcomes : {counts_list}")
display(plot_histogram(counts_list, title="GHZ state"))
{'111': 480, '000': 503, '101': 8, '100': 9, '001': 3, '011': 6, '010': 10, '110': 5}
Outcomes : {'111': 480, '000': 503, '101': 8, '100': 9, '001': 3, '011': 6, '010': 10, '110': 5}

Code output

2.5 대규모 Qubit으로 확장하기

양자 컴퓨팅에서 유틸리티 규모의 작업은 분야의 발전을 위해 매우 중요합니다. 이러한 작업은 훨씬 더 큰 규모의 계산을 필요로 하며, 100개 이상의 Qubit과 1000개 이상의 Gate를 사용하는 Circuit을 다루게 됩니다. 이 예제에서는 GHZ 문제를 n=10n=10 Qubit으로 확장하는 작은 첫걸음을 내딛습니다. Qiskit 패턴 워크플로를 사용하며, 최종적으로 기댓값 Z0Zi\langle Z_0 Z_i \rangle를 측정합니다.

Step 1. 문제 매핑

nn-Qubit GHZ 상태(본질적으로 확장된 Bell 상태)를 준비하는 QuantumCircuit을 반환하는 함수를 작성하고, 그 함수를 사용해 10-Qubit GHZ 상태를 준비한 후 측정할 관측량을 수집합니다.

def get_qc_for_n_qubit_GHZ_state(n: int) -> QuantumCircuit:

qc = QuantumCircuit(n)
qc.h(0)
for i in range(n-1):
qc.cx(i, i+1)
return qc
n = 10
qc_n_GHZ = get_qc_for_n_qubit_GHZ_state(n)
qc_n_GHZ.draw("mpl")

Quantum circuit diagram

다음으로 관심 있는 연산자를 매핑합니다. 이 예제에서는 Qubit 간 거리가 멀어질수록의 동작을 살펴보기 위해 Qubit 사이의 ZZ 연산자를 사용합니다. 거리가 먼 Qubit 간의 기댓값이 점점 부정확해진다면(손상된다면) 존재하는 노이즈 수준을 알 수 있습니다.

# ZZII...II, ZIZI...II, ... , ZIII...IZ
operator_strings = [
"Z" + i * "I" + "Z" + "I" * (n-i-2) for i in range(n-1)
]
print(operator_strings)
print(len(operator_strings))

operators = [SparsePauliOp(operator) for operator in operator_strings]
['ZZIIIIIIII', 'ZIZIIIIIII', 'ZIIZIIIIII', 'ZIIIZIIIII', 'ZIIIIZIIII', 'ZIIIIIZIII', 'ZIIIIIIZII', 'ZIIIIIIIZI', 'ZIIIIIIIIZ']
9

Step 2. 양자 Backend 실행을 위한 문제 최적화

Circuit과 관측량을 Backend의 ISA에 맞게 변환합니다.

# Convert to an ISA circuit and layout-mapped observables.
pm = generate_preset_pass_manager(backend=backend, optimization_level=2)
isa_circuit = pm.run(qc_n_GHZ)
isa_operators_list = [operator.apply_layout(isa_circuit.layout) for operator in operators]

Step 3. Backend에서 실행

Job을 제출하고, 하드웨어에서 실행하는 경우 동적 디커플링(dynamical decoupling)이라는 오류 억제 기법을 사용해 오류를 줄입니다. 복원력 레벨(resilience level)은 오류에 대한 복원력을 얼마나 높일지를 지정합니다. 레벨이 높을수록 더 정확한 결과를 얻을 수 있지만, 처리 시간이 더 오래 걸립니다. 아래 코드에서 설정한 옵션에 대한 자세한 설명은 Qiskit Runtime 오류 완화 설정을 참고하세요.

# Submit the circuit to Estimator
job = estimator.run([(isa_circuit, isa_operators_list)])
job_id = job.job_id()
/Users/jma/miniconda3/envs/3122/lib/python3.12/site-packages/qiskit_ibm_runtime/fake_provider/local_service.py:187: UserWarning: The resilience_level option has no effect in local testing mode.
warnings.warn("The resilience_level option has no effect in local testing mode.")

Step 4. 결과 후처리

실제 하드웨어에서 얽힌 양자 상태의 동작을 더 잘 이해하기 위해, Z 기저에서 Qubit 간의 쌍별 상관관계를 분석합니다. 구체적으로, Qubit 0이 각 Qubit i와 얼마나 강하게 상관되어 있는지를 측정하는 기댓값 ⟨Z₀Zᵢ⟩를 살펴봅니다. 특히 다음 값을 플롯합니다.

ZiZ0/Z1Z0\langle Z_i Z_0 \rangle / \langle Z_1 Z_0 \rangle

플롯에서 ZiZ0/Z1Z0\langle Z_i Z_0 \rangle / \langle Z_1 Z_0 \rangle 의 값이 어떻게 나타날 것으로 예상하나요?

선택지:

a) ii가 증가할수록 감소한다

b) 1로 일정하다

c) 1 근처에서 작은 편차를 보인다

d) ii의 홀수/짝수 값에 따라 1과 0이 교대로 나타난다

data = list(range(1, len(operators) + 1))  # Distance between the Z operators
result = job.result()[0]
values = result.data.evs # Expectation value at each Z operator.
values = [
v / values[0] for v in values
] # Normalize the expectation values to evaluate how they decay with distance.

plt.plot(data, values, marker="o", label=f"{n}-qubit GHZ state")
plt.xlabel("Distance between qubits $i$")
plt.ylabel(r"$\langle Z_i Z_0 \rangle / \langle Z_1 Z_0 \rangle $")
plt.legend()
plt.show()

Plot output

이 플롯에서 이상적인 시뮬레이션에서는 모든 Z0Zi\langle Z_0 Z_i \rangle가 1이어야 하지만, Z0Zi\langle Z_0 Z_i \rangle가 값 1 근처에서 변동하는 것을 확인할 수 있습니다.

보면 알 수 있듯이, 10 Qubit 실험의 결과는 좋지만 여전히 일부 오류가 있습니다. 결과를 개선하는 한 가지 방법은 GHZ 상태를 더 효율적으로 구현하는 것입니다.

일반적으로 GHZ 상태는 계단식 CNOT Gate 시퀀스로 구현합니다. 하지만 2-Qubit 깊이를 n에서 n/2 이하로 줄이는, 더 효율적인 방법으로 GHZ 상태를 구현할 수 있습니다.

Circuit의 결과가 얼마나 정확할지, 또는 노이즈가 얼마나 적을지를 벤치마킹하는 중요한 지표 중 하나는 2-Qubit Gate 깊이입니다. 2-Qubit Gate의 오류율이 단일 Qubit Gate보다 약 10배 높기 때문에, 전체 Circuit의 오류를 지배하게 됩니다. 다음 코드를 사용하여 Circuit의 2-Qubit Gate 깊이를 구할 수 있습니다.

qc.depth(lambda x: x.operation.num_qubits == 2)
def better_ghz(n):
"fan out"
s = int(n / 2)
qc = QuantumCircuit(n)
qc.h(s)
for m in range(s, 0, -1):
qc.cx(m, m - 1)
if not (n % 2 == 0 and m == s):
qc.cx(n - m - 1, n - m)
return qc

better_ghz(n).draw("mpl")

Quantum circuit diagram

# Check 2-qubit gate depth before transpilation
qc_better_ghz = better_ghz(n)
qc_better_ghz.depth(lambda x: x.operation.num_qubits == 2)
5

여기서 흥미로운 점은, 다른 방식으로 프로그래밍하는 방법을 생각해냄으로써 실행하려는 Circuit의 양자 깊이(quantum depth)를 줄일 수 있었다는 것입니다. 하지만 이런 영리한 트릭에 의존할 수 없는 상황과 알고리즘도 있습니다. 이런 경우에 Transpiler가 유용하게 활용됩니다. Transpiler는 이러한 모든 측면을 효율적으로 최적화해 주므로, 우리가 너무 많이 걱정할 필요가 없습니다.

3. 정보 인코딩

3.1 진폭 인코딩

이제 양자 Circuit을 구성하는 방법을 살펴보았으니, 고전 정보를 양자 상태로 인코딩하는 방법을 탐구해 보겠습니다. 강력한 방법 중 하나는 진폭 인코딩(amplitude encoding)으로, 양자 상태의 진폭이 고전 벡터의 구성 요소를 나타냅니다.

간단한 예시를 살펴보겠습니다. 고전 벡터를 인코딩하고 싶다고 가정해 보세요.

x=[x0x1x2x3]\vec{x} = \begin{bmatrix} x_0 \\ x_1 \\ x_2 \\ x_3 \end{bmatrix}

를 두 개의 Qubit 양자 상태로 인코딩하는 것이 목표입니다. 목표는 다음 양자 상태를 준비하는 것입니다:

ψ=x000+x101+x210+x311\ket{\psi} = x_0\ket{00} + x_1\ket{01} + x_2\ket{10} + x_3\ket{11}

여기서 x0,x1,x2,x3Rx_0, x_1, x_2, x_3 \in \mathbb{R} (또는 C\mathbb{C})이고, 벡터는 다음과 같이 정규화되어 있습니다:

x02+x12+x22+x32=1|x_0|^2 + |x_1|^2 + |x_2|^2 + |x_3|^2 = 1

이제 특정 예시를 고려해 보겠습니다: x=[0.8924,0.3696,0.2391,0.0990]\vec{x} = [0.8924, 0.3696, 0.2391, 0.0990]

그러면 해당 양자 상태는 다음과 같습니다:

ψ=0.892400+0.369601+0.239110+0.099011\begin{aligned} \ket{\psi} &= 0.8924\,\ket{00} + 0.3696\,\ket{01} + 0.2391\,\ket{10} + 0.0990\,\ket{11} \end{aligned}

이 상태는 Qubit 0과 1에 대해 각각 π/6\pi/6π/4\pi/4 각도의 회전 Gate RyR_y의 조합을 사용하여 준비할 수 있습니다.

from qiskit import QuantumCircuit
from qiskit_aer import AerSimulator
import numpy as np

qc = QuantumCircuit(2)

qc.ry(np.pi / 6, 0)
qc.ry(np.pi / 4, 1)

simulator = AerSimulator()
qc.save_statevector()
result = simulator.run(qc).result()
statevector = result.get_statevector()

print("Statevector:", statevector)
qc.draw(output="mpl")
Statevector: Statevector([0.8923991 +0.j, 0.23911762+0.j, 0.36964381+0.j,
0.09904576+0.j],
dims=(2, 2))

Quantum circuit diagram

from qiskit.quantum_info import Statevector

# Define our vector
v = np.array([0.8924, 0.3696, 0.2391, 0.0990])
v = v/np.linalg.norm(v)
# Create a statevector from the vector
state = Statevector(v)

# Initialize a quantum circuit with 2 qubits
qc = QuantumCircuit(2)
qc.initialize(state.data, [0, 1])

# Optional: simulate the state
print("Statevector:", state)

# Visualize the circuit
qc.decompose().decompose().decompose().decompose().decompose().draw("mpl")
Statevector: Statevector([0.89242154+0.j, 0.36960892+0.j, 0.23910577+0.j,
0.09900239+0.j],
dims=(2, 2))

Quantum circuit diagram

이로써 회전 Gate를 사용하여 정보를 인코딩하는 방법을 살펴보았습니다.

3.2 각도 인코딩과 매개변수화된 Circuit

양자 컴퓨터에 정보를 인코딩하는 특히 흥미로운 방법은, 특정 함수 집합 f(θ)f(\vec{\theta})를 나타낼 수 있도록 조정 가능한 회전 각도 θ\vec{\theta} 또는 매개변수를 포함하는 양자 Circuit을 설계하는 것입니다. 예를 들어, 다음과 같은 매개변수화된 양자 Circuit을 고려해 보겠습니다:

from qiskit import QuantumCircuit
from qiskit.circuit import Parameter

# Define a symbolic parameter
theta = Parameter("θ")

qc = QuantumCircuit(2)
# We applied a parametrized RX gate
qc.rx(theta, 0)
qc.cx(0, 1)
qc.draw("mpl")

Quantum circuit diagram

수학적으로, 이 Circuit으로 나타낼 수 있는 함수 집합을 분석할 수 있습니다:

CNOT01Rx{0}(θ)00=CNOT01(cos(θ/2)00isin(θ/2)10)=cos(θ/2)00isin(θ/2)11\text{CNOT}_{01} \, R_x^{\{0\}}(\theta) |00\rangle = \text{CNOT}_{01} \left( \cos(\theta/2)\ket{00} - i\sin(\theta/2)\ket{10} \right) = \cos(\theta/2)\ket{00} - i\sin(\theta/2)\ket{11}

이 양자 Circuit으로 나타낼 수 있는 상태의 수는 제한적임이 분명합니다. 예를 들어 10\ket{10} 또는 01\ket{01} 상태는 표현할 수 없습니다. 하지만 적절한 위치에 더 많은 회전을 추가하면 나타낼 수 있는 상태의 집합이 확장됩니다:

from qiskit import QuantumCircuit
from qiskit.circuit import Parameter

# Define a symbolic parameter
theta1 = Parameter("θ1")
theta2 = Parameter("θ2")

qc = QuantumCircuit(2)
qc.rx(theta1, 0)
qc.rx(theta2, 1)
qc.cx(0, 1)
qc.draw("mpl")

Quantum circuit diagram

이 경우, 나타낼 수 있는 양자 상태는 다음과 같습니다:

\begin{align*} \text{CNOT}_{01} \, R_x^{\{1}}(\theta_2) R_x^{\{0}}(\theta_1) \ket{00} &= \text{CNOT}_{01} \, R_x^{\{1}}(\theta_2)\left( \cos(\theta_1/2)\ket{00} - i\sin(\theta_1/2)\ket{10} \right) \\ &= \text{CNOT}_{01}\left( \cos(\theta_1/2)\cos(\theta_2/2)\ket{00} - i\cos(\theta_1/2)\sin(\theta_2/2)\ket{01} \right. \\ &\quad \left. - i\sin(\theta_1/2)\cos(\theta_2/2)\ket{10} + \sin(\theta_1/2)\sin(\theta_2/2)\ket{11} \right) \\ &= \cos(\theta_1/2)\cos(\theta_2/2)\ket{00} - i\cos(\theta_1/2)\sin(\theta_2/2)\ket{01} \\ &\quad + \sin(\theta_1/2)\sin(\theta_2/2)\ket{10} - i\sin(\theta_1/2)\cos(\theta_2/2)\ket{11} \end{align*}

이 Circuit은 이전 것에 비해 더 넓은 양자 상태 집합을 생성함을 알 수 있습니다. 특히, 이전 Circuit에서는 불가능했던 01\ket{01} 또는 10\ket{10}에 대한 0이 아닌 진폭을 가진 상태를 이제 생성할 수 있습니다. 하지만 이 Circuit은 여전히 보편적인 양자 상태 생성기는 아닙니다. 그럼에도 특정 함수를 나타내는 데 있어 어느 정도의 유연성을 갖춘 Circuit을 설계할 만큼 충분히 표현력이 있을 수 있습니다. 일반적으로, 독립적인 매개변수(각도)를 더 많이 도입할수록 Circuit이 임의의 양자 상태를 근사하는 표현력이 높아집니다.

Ansatz와 Circuit 라이브러리

이러한 종류의 매개변수화된 양자 Circuit은 Ansatz를 구성하는 데 사용될 수 있습니다. Ansatz는 문제의 해를 근사하려는 시험 양자 상태입니다. 이 Ansatz는 변분 양자 알고리즘(Variational Quantum Algorithms)의 핵심 구성 요소로, 양자 컴퓨터를 사용하여 비용 함수를 평가하고 고전 최적화기를 사용하여 이를 최소화하는 하이브리드 양자-고전 알고리즘의 한 분류입니다. 이 주제들에 대해서는 이후 단원에서 자세히 다루겠지만, 지금은 Qiskit의 Circuit 라이브러리를 사용하여 간단한 Ansatz를 구성하는 방법을 소개하겠습니다.

from qiskit.circuit.library import efficient_su2

SU2_ansatz = efficient_su2(4, su2_gates=["rx", "y"], entanglement="linear", reps=1)
SU2_ansatz.decompose().draw(output="mpl")

Quantum circuit diagram

qiskit.circuit.libraryefficient_su2 함수를 사용하여 간단한 Ansatz를 구성하는 방법을 살펴보았습니다. 이 Ansatz는 매개변수 θ\vec{\theta}를 조정하여 광범위한 양자 상태를 생성할 수 있습니다.

결론

이 노트북에서는 양자 Gate 구성부터 관측값 정의 및 측정에 이르기까지 양자 Circuit을 구성하는 방법을 배웠고, 이러한 Circuit을 시뮬레이터와 실제 양자 하드웨어 모두에서 효율적으로 실행하는 방법도 살펴보았습니다. 또한 실제 양자 장치에서 작업할 때 오류를 최소화하기 위한 신중한 Circuit 설계의 중요성과 GHZ 상태 예시를 통한 더 많은 Qubit으로의 Circuit 확장 전략도 확인했습니다. 나아가 진폭 인코딩 및 각도 인코딩을 포함하여 고전 정보를 양자 상태로 인코딩하는 다양한 기법을 탐구했습니다. 이 모든 것을 바탕으로, 이제 다음 세션으로 넘어가 양자 알고리즘 작업을 시작할 준비가 완벽히 갖추어졌습니다.

VSCode에 Qiskit 코드 어시스턴트 설치하기

링크를 클릭하고 안내에 따라 진행하세요.

보너스: 양자 텔레포테이션

양자 텔레포테이션이라는 용어를 들으면, 물체를 한 곳에서 분해하여 멀리 떨어진 곳에 재현하는 미래적인 SF 기술을 떠올릴 수 있습니다. 하지만 양자 텔레포테이션은 그런 것과는 전혀 다릅니다. 실제로 텔레포테이션되는 것은 물질이 아니라 정보입니다.

양자 텔레포테이션은 Qubit의 양자 상태를 한 위치에서 다른 위치로 전송할 수 있게 해주는 프로토콜입니다. 이 전송은 순간적으로 이루어지는 것처럼 보이지만, 물리 법칙을 위반하지 않습니다. 어떻게 가능할까요? 자세히 살펴보겠습니다!

양자 텔레포테이션은 송신자(Alice)가 두 가지 핵심 자원을 사용하여 Qubit q의 상태 ψ|\psi\rangle를 수신자(Bob)에게 전달할 수 있게 해주는 프로토콜입니다. 두 가지 자원은 공유된 얽힘 Qubit 쌍 ab, 그리고 두 비트의 고전적 통신 c0c1입니다.

이 프로토콜에 필요한 것은 다음과 같습니다:

  • q: Alice의 Qubit으로, 초기에 텔레포테이션하려는 상태 ψ|\psi\rangle에 있습니다.
  • a: Alice가 보유한 공유 얽힘 쌍의 절반.
  • b: Bob이 보유한 공유 얽힘 쌍의 절반.
  • c0, c1: Alice의 측정 결과를 저장하는 고전 비트.

그렇다면 어떻게 작동할까요? 워크플로우는 다음과 같습니다.

  1. q에 Alice의 상태 ψ|\psi\rangle 준비. 검증을 위해 +|+\rangle와 같은 특정 상태를 생성합니다.
  2. 얽힘 생성: ab 사이에 Bell 쌍을 생성합니다.
  3. Alice의 연산: Alice는 자신의 두 Qubit(qa)에 "Bell 측정"을 수행하고 고전적 결과를 c0c1에 저장합니다.
  4. 고전적 통신: Alice는 자신의 두 고전 비트(c0, c1)를 Bob에게 전송합니다.
  5. Bob의 수정: Bob은 자신이 받은 c0c1의 값에 따라 자신의 Qubit(b)에 특정 양자 Gate(X 및/또는 Z)를 적용합니다.

모든 것이 올바르게 수행되면, Bob의 Qubit b는 Alice의 원래 q 상태인 ψ|\psi\rangle로 끝나게 됩니다!

양자 텔레포테이션에 대한 더 심층적인 설명과 탐구, 이 프로토콜이 왜 작동하는지에 대한 수학적 설명을 포함한 내용은 IBM Quantum Learning 리소스를 참조할 수 있습니다: 양자 텔레포테이션. 이는 양자 정보의 기초 과정의 일부입니다.


import matplotlib.pyplot as plt
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit_aer import AerSimulator
from qiskit.visualization import plot_histogram, plot_bloch_multivector

# Define individual quantum registers for each qubit
q = QuantumRegister(1, name='q') # message qubit
a = QuantumRegister(1, name='a') # Alice's entangled qubit
b = QuantumRegister(1, name='b') # Bob's entangled qubit

# Classical register for Alice's measurements
cr_alice = ClassicalRegister(2, name='c_alice')

# Create quantum circuit
teleport_qc = QuantumCircuit(q, a, b, cr_alice, name='Teleportation')

# Step 1: Prepare message state |+⟩ on q
teleport_qc.h(q[0])
teleport_qc.barrier()

# Step 2: Create entanglement between a and b
teleport_qc.h(a[0])
teleport_qc.cx(a[0], b[0])
teleport_qc.barrier()

# Step 3: Alice's Bell measurement
teleport_qc.cx(q[0], a[0])
teleport_qc.h(q[0])
teleport_qc.barrier()

# Step 4: Alice measures q and a
teleport_qc.measure(q[0], cr_alice[0])
teleport_qc.measure(a[0], cr_alice[1])
teleport_qc.barrier()

# Step 5: Bob's conditional measurements
with teleport_qc.if_test((cr_alice[1], 1)):
teleport_qc.x(b[0])
with teleport_qc.if_test((cr_alice[0], 1)):
teleport_qc.z(b[0])

# Draw the circuit
teleport_qc.draw(output='mpl')

Quantum circuit diagram

프로토콜을 실행한 후 핵심 질문이 생깁니다. 텔레포테이션이 성공했는지 어떻게 확인할 수 있을까요? 프로토콜 이후 Bob의 Qubit 상태를 직접 '볼' 수는 없습니다. 하지만 Alice의 초기 상태 ψ|\psi\rangle를 우리가 준비했기 때문에(+|+\rangle를 선택했습니다), 특별한 종류의 시뮬레이션을 사용하여 Bob의 Qubit b가 그 동일한 상태로 끝났는지 확인할 수 있습니다.

save_statevector가 적용된 AerSimulator를 사용하여 Bob의 Qubit b가 Alice의 원래 상태(+|+\rangle)로 끝났는지 확인하겠습니다. 이 시뮬레이터는 최종 양자 상태 벡터를 계산합니다. 그런 다음 plot_bloch_multivector를 사용하여 Alice의 초기 상태(q)와 비교하여 Bob의 Qubit(b)를 시각화합니다.

# Simulate the teleportation circuit
sv_simulator = AerSimulator(method='statevector')
teleport_qc_sv = teleport_qc.copy()
teleport_qc_sv.save_statevector()

# Execute the circuit on the statevector simulator
job_sv = sv_simulator.run(teleport_qc_sv)
result_sv = job_sv.result()

# Get the final statevector
final_statevector = result_sv.get_statevector()
print("Visualizing final qubit states:")
display(plot_bloch_multivector(final_statevector))
print("Note that Alice's qubits have collapsed to |00⟩, |01⟩, |10⟩, or |11⟩, while Bob's qubit is in the original state |+⟩.")
Visualizing final qubit states:

Quantum circuit diagram

Note that Alice's qubits have collapsed to |00⟩, |01⟩, |10⟩, or |11⟩, while Bob's qubit is in the original state |+⟩.

시각화에서 볼 수 있듯이, Alice에 속한 첫 번째 두 Qubit은 0 또는 1로 붕괴되었습니다. 한편, 세 번째 블로흐 구(Bloch sphere)로 표현된 Bob의 세 번째 Qubit은 x축을 향해 있어 +|+\rangle 상태임을 나타내므로, 양자 텔레포테이션 프로토콜을 성공적으로 구현했습니다!

요약

이 시점에서 우리가 달성한 것을 간략히 요약하겠습니다:

  • Alice는 알 수 없는 양자 상태를 Bob에게 전달했습니다.
  • 어떠한 물리적 입자도 전송되지 않았습니다.
  • Alice의 Qubit에 있던 원래 상태는 파괴되었으며, 이는 복제 불가 정리(No-Cloning theorem)에 부합합니다.

그러나 양자 텔레포테이션은 여전히 고전적 통신(Alice의 측정 결과를 Bob에게 전송하는 것)이 필요하며, 이것이 이 프로세스가 빛보다 빠른 정보 전달을 허용하지 않고 알려진 모든 물리 법칙과 완전히 일치하는 이유를 설명합니다.